//
// ComponentManager.cs
//
// Copyright (C) 2007 Fredrik Hedberg <fredrik@avafan.com>
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

using Dashboard;
using Dashboard.Util;

namespace Dashboard.Engine
{
	public class ComponentManager : IService
	{
		private const string BLACKLIST = "DASHBOARD_COMPONENT_BLACKLIST";
		private const string WHITELIST = "DASHBOARD_COMPONENT_WHITELIST";

		private List<IComponent> components = new List<IComponent> ();
		private List<string> component_blacklist = new List<string> ();
		private List<string> component_whitelist = new List<string> ();

		private const string assembly_pattern = "*.dll";
		private FileSystemWatcher watcher = null;
		
		public ComponentManager ()
		{
		}

		public void Initialize ()
		{
			// Setup the component black and white lists.
			
			if (Environment.GetEnvironmentVariable (BLACKLIST) != null) {
				foreach (string name in Environment.GetEnvironmentVariable (BLACKLIST).Split (' '))
					component_blacklist.Add (name.ToLower ());
			}

			if (Environment.GetEnvironmentVariable (WHITELIST) != null) {
				foreach (string name in Environment.GetEnvironmentVariable (WHITELIST).Split (' '))
					component_whitelist.Add (name.ToLower ());
			}

			// Scan the environment for component assemblies
			// and initialize the allowed components.

			Engine.Log.Info ("Loading components from assemblies");

			Stopwatch stopwatch = new Stopwatch ();
			stopwatch.Start ();
			
			List<Assembly> assemblies = new List<Assembly> ();
			assemblies.Add (Assembly.GetExecutingAssembly());
			assemblies.AddRange (ReflectionFu.ScanEnvironmentForAssemblies ("DASHBOARD_COMPONENTS_PATH", PathFinder.Components));
			
			foreach (Assembly assembly in assemblies)
				InitializeAssembly (assembly);

			// Monitor the filesystem and initialize new component
			// as they are installed.

			stopwatch.Stop ();
			Engine.Log.Info ("Finished loading components ({0})", stopwatch);

			if (Directory.Exists (PathFinder.Components))
				MonitorComponents ();
		}
		
		public void Dispose ()
		{
			if (watcher != null)
				watcher.EnableRaisingEvents = false;
			
			Engine.Log.Info ("Unloading components");

			foreach (IComponent component in components)
				DisposeComponent (component);
		}

		private void MonitorComponents ()
		{
#if LINUX && I_LIKE_CATS
			if (Inotify.Enabled) {
				Inotify.Subscribe (PathFinder.Components, OnInotifyEvent, Inotify.EventType.Create | Inotify.EventType.CloseWrite);
			} else {
#endif
				
				watcher = new FileSystemWatcher ();
				watcher.Path = PathFinder.Components;
				watcher.Filter = assembly_pattern;
				watcher.IncludeSubdirectories = false;
				watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
				
				watcher.Created += OnFileSystemEvent;
				watcher.EnableRaisingEvents = true;
#if LINUX && I_LIKE_CATS
			}
#endif
		}

		private void InitializeAssembly (string path)
		{
			InitializeAssembly (Assembly.Load (path));
		}

		private void InitializeAssembly (Assembly assembly)
		{
			List<Type> assembly_types = ReflectionFu.ScanAssemblyForInterface (assembly, typeof (IComponent));
			List<IComponent> assembly_components = new List<IComponent> ();

			foreach (Type type in assembly_types) {
				IComponent component = InitializeComponent (type);
				
				if (component != null)
					assembly_components.Add (component);
			}

			AssemblyName an = assembly.GetName ();
			Engine.Log.Debug ("Loaded {0} components from assembly: {1}", assembly_components.Count, an.Name);
		}

		private IComponent InitializeComponent (Type type)
		{
			IComponent component = null;
			Stopwatch stopwatch = new Stopwatch ();
			
			try {
				component = Activator.CreateInstance (type) as IComponent;
				
				if (!ValidateComponentUsage (component.Name))
					return null;

				stopwatch.Start ();
				
				component.Initialize ();
				components.Add (component);

				stopwatch.Stop ();

				Engine.Log.Debug ("Component started ({0}, {1})", component.Name, stopwatch);
			} catch (Exception ex) {
				Engine.Log.Warn ("Exception while instantiating component: {0}", component.Name);
				Engine.Log.Warn (ex);
				return null;
			}
			
			return component;
		}

		private void DisposeComponent (IComponent component)
		{
			if (!components.Contains (component))
				throw new ApplicationException ("Component not active");

			try {
				component.Dispose ();
				Engine.Log.Debug ("Component disposed ({0})", component.Name);
			} catch (Exception ex) {
				Engine.Log.Warn ("Exception while disposing component: {0} ({1}: {2})", 
						 component.GetType (), ex.GetType (), ex.Message);
			}
		}

		private bool ValidateComponentUsage (string name)
		{
			name = name.ToLower ();
			
			if (component_whitelist.Contains (name))
				return true;

			if (component_whitelist.Count != 0)
				return false;

			if (component_blacklist.Contains (name))
				return false;

			return true;
		}
		
		private void OnFileSystemEvent (object o, FileSystemEventArgs args) 
		{
			InitializeAssembly (args.FullPath);
		}

#if LINUX && I_LIKE_CATS
		private void OnInotifyEvent (Inotify.Watch watch, string path, string subitem, string srcpath, Inotify.EventType type)
		{
			if (!StringFu.GlobMatch (assembly_pattern, subitem))
				return;

			InitializeAssembly (Path.Combine (path, subitem));
		}
#endif

		public List<IComponent> Components
		{
			get { return components; }
		}		
	}
}
